package site.isscloud.common.core.service.impl;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.transaction.annotation.Transactional;
import site.isscloud.common.core.service.IExcelImportService;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Excel导入服务基类
 *
 * 提供通用的Excel数据导入框架，子类只需实现特定的业务逻辑方法即可完成数据导入功能。
 *
 * 使用前提：
 * 1. 数据实体类需要有唯一标识字段（如编码、ID等）
 * 2. 数据需要支持批量插入和更新操作
 * 3. 导入数据需要支持事务回滚
 *
 * @param <T> 数据实体类型
 */
public abstract class BaseExcelImportService<T> implements IExcelImportService<T> {

    protected final Logger logger = LoggerFactory.getLogger(this.getClass());

    /**
     * 批量导入数据 - 核心导入方法
     *
     * 导入流程：
     * 1. 参数校验
     * 2. 加载现有数据
     * 3. 遍历导入数据
     *   3.1 数据验证
     *   3.2 处理数据关系
     *   3.3 检查数据是否存在
     *   3.4 执行插入或更新操作
     * 4. 返回导入结果
     *
     * @param dataList 待导入的数据列表，不能为空
     * @param updateSupport 是否支持更新已存在的数据
     *                    true: 存在则更新，false: 存在则跳过
     * @param params 额外参数，可用于传递业务特定的参数
     * @return 导入结果消息，包含成功和失败统计信息
     * @throws RuntimeException 当导入数据为空或导入过程中出现错误时抛出
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public String importData(List<T> dataList, Boolean updateSupport, Map<String, Object> params) {
        if (dataList == null || dataList.isEmpty()) {
            throw new RuntimeException("导入数据不能为空！");
        }

        int successNum = 0;
        int failureNum = 0;
        StringBuilder successMsg = new StringBuilder();
        StringBuilder failureMsg = new StringBuilder();

        // 初始化数据映射：用于记录本次导入过程中已处理的数据
        Map<String, Object> processedData = new HashMap<>();
        // 加载数据库中已存在的数据：用于检查数据是否已存在
        Map<String, Object> existingData = loadExistingData();

        for (T data : dataList) {
            try {
                // 步骤1: 数据验证 - 检查数据的合法性
                String validationError = validateData(data);
                if (validationError != null) {
                    throw new RuntimeException(validationError);
                }

                // 步骤2: 处理数据关系 - 如父子关系、外键关联等
                processDataRelations(data, processedData, existingData);

                // 步骤3: 检查数据是否存在
                String uniqueKey = getUniqueKey(data);
                if (isDataExists(uniqueKey, existingData, processedData)) {
                    if (updateSupport) {
                        // 更新已存在的数据
                        updateData(data);
                        successNum++;
                        successMsg.append("<br/>").append(successNum).append("、").append(getDataName(data)).append(" 更新成功");
                    } else {
                        failureNum++;
                        failureMsg.append("<br/>").append(failureNum).append("、").append(getDataName(data)).append(" 已存在");
                    }
                } else {
                    // 步骤4: 插入新数据
                    insertData(data);
                    successNum++;
                    successMsg.append("<br/>").append(successNum).append("、").append(getDataName(data)).append(" 导入成功");

                    // 记录已处理的数据，用于后续数据的关系处理
                    processedData.put(uniqueKey, getDataId(data));
                }
            } catch (Exception e) {
                failureNum++;
                String msg = "<br/>" + failureNum + "、" + getDataName(data) + " 导入失败：";
                failureMsg.append(msg).append(e.getMessage());
                logger.error(msg, e);
            }
        }

        // 构建最终结果消息
        if (failureNum > 0) {
            failureMsg.insert(0, "很抱歉，导入失败！共 " + failureNum + " 条数据格式不正确，错误如下：");
            throw new RuntimeException(failureMsg.toString());
        } else {
            successMsg.insert(0, "恭喜您，数据已全部导入成功！共 " + successNum + " 条");
        }

        return successMsg.toString();
    }

    // ========== 抽象方法 - 需要子类根据具体业务实现 ==========

    /**
     * 加载现有数据 - 从数据库加载已存在的数据
     *
     * 用途：用于检查导入数据是否已存在，避免重复导入
     * 要求：返回的Map中，key为数据的唯一标识，value为数据的主键ID或其他标识
     *
     * @return 现有数据的映射表，key: 唯一标识, value: 数据ID
     */
    protected abstract Map<String, Object> loadExistingData();

    /**
     * 获取数据的唯一标识键
     *
     * 用途：用于判断数据是否存在的依据
     * 示例：对于用户数据可能是用户名，对于商品数据可能是商品编码
     *
     * @param data 数据对象
     * @return 数据的唯一标识字符串
     */
    protected abstract String getUniqueKey(T data);

    /**
     * 获取数据的显示名称
     *
     * 用途：在导入结果消息中显示，便于用户识别
     *
     * @param data 数据对象
     * @return 数据的显示名称
     */
    protected abstract String getDataName(T data);

    /**
     * 获取数据的主键ID
     *
     * 用途：用于记录已处理数据的ID，便于后续数据建立关系
     *
     * @param data 数据对象
     * @return 数据的主键ID
     */
    protected abstract Object getDataId(T data);

    /**
     * 检查数据是否存在
     *
     * 用途：判断数据是否已存在（在数据库或本次导入中）
     *
     * @param uniqueKey 数据的唯一标识
     * @param existingData 数据库中已存在的数据映射
     * @param processedData 本次导入已处理的数据映射
     * @return true表示数据已存在，false表示不存在
     */
    protected abstract boolean isDataExists(String uniqueKey, Map<String, Object> existingData, Map<String, Object> processedData);

    /**
     * 插入新数据
     *
     * 用途：将数据插入到数据库
     *
     * @param data 要插入的数据对象
     */
    protected abstract void insertData(T data);

    /**
     * 更新已存在的数据
     *
     * 用途：更新数据库中已存在的数据
     *
     * @param data 要更新的数据对象
     */
    protected abstract void updateData(T data);

    /**
     * 数据验证 - 验证数据的合法性
     *
     * 用途：检查数据的必填字段、格式、业务规则等
     *
     * @param data 要验证的数据对象
     * @return 验证错误信息，返回null表示验证通过
     */
    public String validateData(T data) {
        // 默认实现，子类可以重写
        return null;
    }

    /**
     * 处理数据关系 - 处理数据的关联关系
     *
     * 用途：处理如父子关系、外键关联、编码转换等
     * 示例：将父级编码转换为父级ID，处理部门层级关系等
     *
     * @param data 当前处理的数据对象
     * @param processedData 本次导入已处理的数据映射
     * @param existingData 数据库中已存在的数据映射
     */
    public void processDataRelations(T data, Map<String, Object> processedData, Map<String, Object> existingData) {
        // 默认实现，子类可以重写
    }
}